////////////////////////////////////////////////////////////////
// filename: particle.cpp
// description: source file for the particle class
// author: Jay Tomten (jaytomten@gmail.com)
// notes:

// namespace

// defines

// includes
#if defined(__APPLE__)
#include <OpenGL/gl.h>
#else
#include <GL/gl.h>
#endif
#include <SDL/SDL.h>
#include <iostream>
#include "particle.h"

// globals

////////////////////////////////////////////////////////////////
// constructor
Particle::Particle() {

    // use Init() or alt constructor

}

////////////////////////////////////////////////////////////////
// alternate constructor that calls Init()
Particle::Particle(int type, float x, float y, float z) {

    Init(type, x, y, z);

}

////////////////////////////////////////////////////////////////
// destructor
Particle::~Particle() {

    delete pArray;

}

////////////////////////////////////////////////////////////////
// Init() - set up the particle system we want
void Particle::Init(int type, float x, float y, float z) {

    // crappy old school switch based loading!
    switch(type) {
    case PARTICLE_EXPLOSION:
        // class vars
        alive = true;
        t = PARTICLE_EXPLOSION;
        max = 1000;
        shimmer = true;
        //timer = 0.0f;
        playTime = 0.2f;
	    playedTime = 0.0f;
	    pps = 1000.0f;
	    ppsVar = 100.00f;
        // struct vars
        posStart.Init(x, y, z);
	    posStartVar.Init(0.0f, 0.0f, 0.0f);
	    posStartChange.Init(0.0f, 0.0f, 0.0f);
	    velStart.Init(0.0f, 0.0f, 0.0f);
	    velVar.Init(5.0f, 5.0f, 5.0f);
	    velChange.Init(0.3f, 0.3f, 0.3f);
        colStart.Init(1.0f, 1.0f, 1.0f, 0.6f);
        colVar.Init(0.0f, 0.2f, 0.4f, 0.1f);
        colChange.Init(-0.0f, -2.0f, -4.0f, -0.2f);
	    forceStart.Init(0.0f, 0.0f, 0.0f);
	    forceVar.Init(1.0f, 1.0f, 1.0f);
	    forceChange.Init(-0.1f, -1.31f, 0.0f);
        sizeStart = 8.0f;
        sizeVar = 5.0f;
        sizeChange = -1.7f;
        lifeStart = 0.5f;
        lifeVar = 0.5f;
	    break;
    case PARTICLE_SMOKE:
        // class vars
        alive = true;
        t = PARTICLE_SMOKE;
	    max = 1000;
	    shimmer = false;
        //timer = 0.0f;
        playTime = 5.0f;
	    playedTime = 0.0f;
	    pps = 100.0f;
	    ppsVar = 20.00f;
        // struct vars
        posStart.Init(x, y, z);
	    posStartVar.Init(1.0f, 1.0f, 0.0f);
	    posStartChange.Init(0.0f, 0.0f, 0.0f);
	    velStart.Init(0.0f, 4.0f, 0.0f);
	    velVar.Init(1.0f, 3.0f, 1.0f);
	    velChange.Init(-2.5f, -2.5f, -2.5f);
        colStart.Init(0.0f, 0.0f, 0.0f, 0.4f);
        colVar.Init(0.01f, 0.01f, 0.01f, 0.1f);
        colChange.Init(0.1f, 0.1f, 0.1f, -0.02f);
	    forceStart.Init(0.0f, 3.0f, 0.0f);
	    forceVar.Init(2.0f, 0.0f, 1.0f);
	    forceChange.Init(0.0f, 0.0f, 0.0f);
        sizeStart = 8.0f;
        sizeVar = 4.0f;
        sizeChange = -1.7f;
        lifeStart = 2.5f;
        lifeVar = 0.5f;
	    break;
    case PARTICLE_BULLETS_F:
        // class vars
        alive = true;
        t = PARTICLE_BULLETS_F;
    	max = 10;
    	shimmer = false;
        //timer = 0.0f;
        playTime = 1.5f;
	    playedTime = 0.0f;
	    pps = 2.0f;
	    ppsVar = 1.0f;
        // struct vars
        posStart.Init(x + 1.0f, y + 0.3f, z);
	    posStartVar.Init(0.0f, 0.0f, 0.0f);
	    posStartChange.Init(1.0f, 0.0f, 0.0f);
	    velStart.Init(20.0f, 0.0f, 0.0f);
        velVar.Init(0.0f, 0.0f, 0.0f);
        velChange.Init(0.0f, 0.0f, 0.0f);
        colStart.Init(0.0f, 0.0f, 0.0f, 1.0f);
        colVar.Init(0.1f, 0.1f, 0.1f, 0.5f);
        colChange.Init(0.0f, 0.0f, 0.0f, -2.0f);
        forceStart.Init(0.0f, 0.0f, 0.0f);
        forceVar.Init(0.0f, 0.0f, 0.0f);
        forceChange.Init(0.0f, 0.0f, 0.0f);
        sizeStart = 2.5f;
        sizeVar = 0.0f;
        sizeChange = -0.0f;
        lifeStart = 0.3f;
        lifeVar = 0.1f;
        break;
    case PARTICLE_BULLETS_E:
        // class vars
        alive = true;
        t = PARTICLE_BULLETS_E;
        max = 10;
        shimmer = false;
        //timer = 0.0f;
        playTime = 1.5f;
        playedTime = 0.0f;
        pps = 2.0f;
        ppsVar = 1.00f;
        // struct vars
        posStart.Init(x - 1.0f, y + 0.3f, z);
        posStartVar.Init(0.0f, 0.0f, 0.0f);
        posStartChange.Init(-1.0f, 0.0f, 0.0f);
        velStart.Init(-20.0f, 0.0f, 0.0f);
        velVar.Init(0.0f, 0.0f, 0.0f);
        velChange.Init(0.0f, 0.0f, 0.0f);
        colStart.Init(0.0f, 0.0f, 0.0f, 1.0f);
        colVar.Init(0.1f, 0.1f, 0.1f, 0.5f);
        colChange.Init(0.0f, 0.0f, 0.0f, -2.0f);
        forceStart.Init(0.0f, 0.0f, 0.0f);
        forceVar.Init(0.0f, 0.0f, 0.0f);
        forceChange.Init(0.0f, 0.0f, 0.0f);
        sizeStart = 2.5f;
        sizeVar = 0.0f;
        sizeChange = -0.0f;
        lifeStart = 0.3f;
        lifeVar = 0.1f;
        break;
    case PARTICLE_BLOOD:
        // class vars
        alive = true;
        t = PARTICLE_BLOOD;
        max = 250;
        shimmer = true;
        //timer = 0.0f;
        playTime = 0.2f;
        playedTime = 0.0f;
        pps = 1500.0f;
        ppsVar = 2.00f;
        // struct vars
        posStart.Init(x, y, z);
        posStartVar.Init(0.0f, 0.0f, 0.0f);
        posStartChange.Init(0.0f, 0.0f, 0.0f);
        velStart.Init(0.0f, 0.0f, 0.0f);
        velVar.Init(3.0f, 3.0f, 3.0f);
        velChange.Init(0.0f, 0.0f, 0.0f);
        colStart.Init(1.0f, 0.0f, 0.0f, 1.0f);
        colVar.Init(0.0f, 0.0f, 0.0f, 1.0f);
        colChange.Init(-0.1f, -0.1f, -1.0f, -0.2f);
        forceStart.Init(0.0f, 0.0f, 0.0f);
        forceVar.Init(0.0f, 0.0f, 0.0f);
        forceChange.Init(-0.1f, -1.31f, 0.0f);
        sizeStart = 4.0f;
        sizeVar = 2.0f;
        sizeChange = -0.7f;
        lifeStart = 0.7f;
        lifeVar = 0.6f;
        break;
    case PARTICLE_RAIN:
        // class vars
        alive = true;
        t = PARTICLE_RAIN;
        max = 5000;
        shimmer = true;
        //timer = 0.0f;
        playTime = 100000.0f;
        playedTime = 0.0f;
        pps = 500.0f;
        ppsVar = 1.00f;
        // struct vars
        posStart.Init(x, y, z);
        posStartVar.Init(150.0f, 0.0f, 10.0f);
        posStartChange.Init(0.0f, 0.0f, 0.0f);
        velStart.Init(0.1f, 2.1f, 0.1f);
        velVar.Init(0.5f, 0.5f, 1.0f);
        velChange.Init(0.0f, 0.0f, 0.0f);
        colStart.Init(0.2f, 0.2f, 0.3f, 0.1f);
        colVar.Init(0.0f, 0.2f, 0.0f, 0.08f);
        colChange.Init(0.0f, 0.0f, 0.0f, 0.0f);
        forceStart.Init(0.0f, 0.0f, 0.0f);
        forceVar.Init(0.0f, 0.0f, 0.0f);
        forceChange.Init(-0.1f, -1.31f, 0.0f);
        sizeStart = 3.0f;
        sizeVar = 1.0f;
        sizeChange = 0.0f;
        lifeStart = 15.0f;
        lifeVar = 0.0f;
        break;
    case PARTICLE_SNOW:
        // class vars
        alive = true;
        t = PARTICLE_SNOW;
        max = 1000;
        shimmer = true;
        //timer = 0.0f;
        playTime = 100000.0f;
        playedTime = 0.0f;
        pps = 20.0f;
        ppsVar = 3.0f;
        // struct vars
        posStart.Init(x, y, z);
        posStartVar.Init(150.0f, 0.0f, 10.0f);
        posStartChange.Init(0.0f, 0.0f, 0.0f);
        velStart.Init(0.1f, 0.1f, 0.1f);
        velVar.Init(0.5f, 1.1f, 1.0f);
        velChange.Init(0.0f, 0.0f, 0.0f);
        colStart.Init(1.1f, 1.1f, 1.1f, 0.1f);
        colVar.Init(0.1f, 0.1f, 0.1f, 0.2f);
        colChange.Init(0.0f, 0.0f, 0.0f, 0.0f);
        forceStart.Init(0.0f, 0.0f, 0.0f);
        forceVar.Init(0.0f, 0.0f, 0.0f);
        forceChange.Init(0.0f, -0.05f, 0.0f);
        sizeStart = 8.0f;
        sizeVar = 4.0f;
        sizeChange = 0.0f;
        lifeStart = 50.0f;
        lifeVar = 0.0f;
	break;
    case PARTICLE_DUST:
        // class vars
        alive = true;
        t = PARTICLE_DUST;
        max = 5000;
        shimmer = true;
        //timer = 0.0f;
        playTime = 100000.0f;
        playedTime = 0.0f;
        pps = 250.0f;
        ppsVar = 0.0f;
        // struct vars
        posStart.Init(x, y, z);
        posStartVar.Init(0.0f, 10.0f, 10.0f);
        posStartChange.Init(0.0f, 0.0f, 0.0f);
        velStart.Init(-40.0f, 0.0f, 0.0f);
        velVar.Init(0.5f, 0.5f, 1.0f);
        velChange.Init(0.0f, 0.0f, 0.0f);
        colStart.Init(0.7f, 0.8f, 0.2f, 0.1f);
        colVar.Init(0.1f, 0.1f, 0.1f, 0.1f);
        colChange.Init(0.0f, 0.0f, 0.0f, 0.0f);
        forceStart.Init(0.0f, 0.0f, 0.0f);
        forceVar.Init(3.0f, 0.5f, 10.0f);
        forceChange.Init(0.0f, 0.0f, 0.0f);
        sizeStart = 5.0f;
        sizeVar = 4.0f;
        sizeChange = 0.0f;
        lifeStart = 250.0f;
        lifeVar = 0.0f;
        break;
    default:
        break;
    }

    pArray = new Part[max]; // init array
    last = 0;               // set to the begining

}

////////////////////////////////////////////////////////////////
// Empty() - returns true if array is close to empty
bool Particle::Empty() {

    return last <= 3; // delete when only 3 particle left

}

////////////////////////////////////////////////////////////////
// Full() - returns true if array is full
bool Particle::Full() {

    return last == max - 1;

}

////////////////////////////////////////////////////////////////
// Add() - adds one particle to array if possible
void Particle::Add() {

    if (!Full() && playTime > playedTime) {
        pArray[last].pos.Init(posStart.x + ((rand()%10000 / 5000.0f - 1.0f) * posStartVar.x),
                              posStart.y + ((rand()%10000 / 5000.0f - 1.0f) * posStartVar.y),
                              posStart.z + ((rand()%10000 / 5000.0f - 1.0f) * posStartVar.z));
        pArray[last].vel.Init(velStart.x + ((rand()%10000 / 5000.0f - 1.0f) * velVar.x),
                              velStart.y + ((rand()%10000 / 5000.0f - 1.0f) * velVar.y),
                              velStart.z + ((rand()%10000 / 5000.0f - 1.0f) * velVar.z));
        pArray[last].col.Init(colStart.r + ((rand()%10000 / 5000.0f - 1.0f) * colVar.r),
                              colStart.g + ((rand()%10000 / 5000.0f - 1.0f) * colVar.g),
                              colStart.b + ((rand()%10000 / 5000.0f - 1.0f) * colVar.b),
                              colStart.a + ((rand()%10000 / 5000.0f - 1.0f) * colVar.a));
        pArray[last].force.Init(forceStart.x + ((rand()%10000 / 5000.0f - 1.0f) * forceVar.x),
                                forceStart.y + ((rand()%10000 / 5000.0f - 1.0f) * forceVar.y),
                                forceStart.z + ((rand()%10000 / 5000.0f - 1.0f) * forceVar.z));
        pArray[last].size = sizeStart + ((rand()%10000 / 5000.0f - 1.0f) * sizeVar);
        pArray[last].life = lifeStart + ((rand()%10000 / 5000.0f - 1.0f) * lifeVar);
        pArray[last].timer = rand()%10000 / 5000.0f - 1.0f;
        last++;
    }

}

////////////////////////////////////////////////////////////////
// Delete() - deletes one particle from array if possible
void Particle::Delete(int i) {

    if (!Empty()) {
        pArray[i] = pArray[last]; // write over the dead particle with the last one in the list
        --last;
    } else alive = false;

}

////////////////////////////////////////////////////////////////
// Update() - updates every particle
bool Particle::Update(float d) {

    if(playTime > playedTime || last > 5) {

        playedTime += d; // update the time this particle effect has played

        // update the actual particles
        for(int i = 0; i < last; i++) { // traverse all the particles and update
            if(pArray[i].life > 0) { // if we have life left
                pArray[i].pos.Init(pArray[i].pos.x + pArray[i].vel.x * d,
                                   pArray[i].pos.y + pArray[i].vel.y * d,
                                   pArray[i].pos.z + pArray[i].vel.z * d);
                pArray[i].vel.Init(pArray[i].vel.x + (velChange.x * d) + (pArray[i].force.x * d),
                                   pArray[i].vel.y + (velChange.y * d) + (pArray[i].force.y * d),
                                   pArray[i].vel.z + (velChange.z * d) + (pArray[i].force.z * d));
                pArray[i].col.Init(pArray[i].col.r + (colChange.r * d),
                                   pArray[i].col.g + (colChange.g * d),
                                   pArray[i].col.b + (colChange.b * d),
                                   pArray[i].col.a + (colChange.a * d));
                pArray[i].force.Init(pArray[i].force.x + (forceChange.x * d),
                                     pArray[i].force.y + (forceChange.y * d),
                                     pArray[i].force.z + (forceChange.z * d));
                pArray[i].size += (sizeChange * d);
                pArray[i].life -= d;
                if(pArray[i].timer > 0.0f)  pArray[i].timer += d;
                else pArray[i].timer += d;
                if(pArray[i].timer > 1.0f)  pArray[i].timer = -0.98f;
                if(pArray[i].timer < -1.0f) pArray[i].timer = 0.98f;
            } else Delete(i); // if the particle is dead, erase it
            if(pArray[i].pos.y <= 0.0 || pArray[i].pos.x < -150.0 || pArray[i].pos.x > 150.0) Delete(i);
        }

        // FIX !!!! update starting pos here !!!!

        // add particles
        int j = (int)(((rand()%10000 / 5000.0f - 1.0f) * ppsVar) * pps * d);
        for(int i = 0; i <= j; ++i) Add();

        return true;

    } else return false;

}

////////////////////////////////////////////////////////////////
// Draw() - draw every particle
void Particle::Draw() {

    float sparkler = 0.0f;
    glPushMatrix();
    if(shimmer) {
        for(int i = 0; i <= last; i++) { // run through the array and draw the particles
            glPointSize(pArray[i].size);
            if(pArray[i].timer > 0.0f) sparkler = pArray[i].timer;
            else sparkler = -pArray[i].timer;
            glColor4f(pArray[i].col.r, pArray[i].col.g, pArray[i].col.b, pArray[i].col.a + sparkler / 2);
            glBegin(GL_POINTS);
                glVertex3f(pArray[i].pos.x, pArray[i].pos.y, pArray[i].pos.z);
            glEnd();
        }
    } else {
        for(int i = 0; i <= last; i++) { // run through the array and draw the particles
            glPointSize(pArray[i].size);
            glColor4f(pArray[i].col.r, pArray[i].col.g, pArray[i].col.b, pArray[i].col.a);
            glBegin(GL_POINTS);
                glVertex3f(pArray[i].pos.x, pArray[i].pos.y, pArray[i].pos.z);
            glEnd();
        }
    }
    glPopMatrix();
    glFlush();

}
